﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Hosting;
using System.Web;
using System.Collections.ObjectModel;

namespace MvcPatch
{
    public class AreaWebFormViewEngine : IViewEngine
    {
        public AreaWebFormViewEngine()
        {
            this.ViewLocationCache = HttpContext.Current == null || HttpContext.Current.IsDebuggingEnabled ?
                DefaultViewLocationCache.Null : new DefaultViewLocationCache();
        }

        public IViewLocationCache ViewLocationCache { get; private set; }

        public string NamespaceBase { get; private set; }

        public readonly static ReadOnlyCollection<string> PartialLocations = new ReadOnlyCollection<string>(
            new List<string>
            {
                "~/Views/{2}{1}/{0}.ascx",
                "~/Views/{2}Shared/{0}.ascx",
                "~/Views/Shared/{0}.ascx"
            });

        public readonly static ReadOnlyCollection<string> ViewLocations = new ReadOnlyCollection<string>(
            new List<string>
            {
                "~/Views/{2}{1}/{0}.aspx",
                "~/Views/{2}Shared/{0}.aspx",
                "~/Views/Shared/{0}.aspx"
            });

        public readonly static ReadOnlyCollection<string> LayoutLocations = new ReadOnlyCollection<string>(
            new List<string>
            {
                "~/Views/{2}{1}/{0}.master",
                "~/Views/{2}Shared/{0}.master",
                "~/Views/Shared/{0}.master"
            });

        private string FindPath(ReadOnlyCollection<string> locations, ControllerContext controllerContext, string name)
        {
            foreach (var format in locations)
            {
                var area = controllerContext.RouteData.Values["area"] as string;
                var part = String.IsNullOrEmpty(area) ? "" : area + "/";

                var virtualPath = String.Format(format,
                    name,
                    controllerContext.RouteData.GetRequiredString("controller"),
                    part);

                if (HostingEnvironment.VirtualPathProvider.FileExists(virtualPath)) return virtualPath;
            }

            return null;
        }

        private string GetCacheKey(ControllerContext controllerContext, string viewName, string type /* view|partial|layout */)
        { 
            var routeData = controllerContext.RouteData;
            return String.Format(
                "areaCacheKey:{0}|{1}|{2}|{3}",
                (routeData.Values["area"] as string) ?? "/missing/",
                routeData.GetRequiredString("controller"),
                viewName,
                type);
        }

        public string FindTemplateLocation(
            ReadOnlyCollection<string> locations,
            ControllerContext controllerContext,
            string name,
            string type,
            bool useCache)
        {
            var cacheKey = this.GetCacheKey(controllerContext, name, type);
            string templateLocation = null;

            if (useCache)
            {
                templateLocation = this.ViewLocationCache.GetViewLocation(controllerContext.HttpContext, cacheKey);
            }

            if (templateLocation == null)
            {
                templateLocation = this.FindPath(locations, controllerContext, name);
                if (templateLocation != null)
                {
                    this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, templateLocation);
                }
            }

            return templateLocation;
        }

        public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
        {
            var location = this.FindTemplateLocation(PartialLocations, controllerContext, partialViewName, "partial", useCache);
            return location == null ? null : new ViewEngineResult(new WebFormView(location), this);
        }

        public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            viewName = String.IsNullOrEmpty(viewName) ? controllerContext.RouteData.GetRequiredString("action") : viewName;
            var viewLocation = this.FindTemplateLocation(ViewLocations, controllerContext, viewName, "view", useCache);
            if (viewLocation == null) return null;

            var masterLocation = String.IsNullOrEmpty(masterName) ? "" :
                this.FindTemplateLocation(LayoutLocations, controllerContext, masterName, "layout", useCache);
            if (masterLocation == null) return null;

            return new ViewEngineResult(new WebFormView(viewLocation, masterLocation), this);
        }

        public void ReleaseView(ControllerContext controllerContext, IView view)
        {
            IDisposable disposable = view as IDisposable;
            if (disposable != null) disposable.Dispose();
        }
    }
}
